#include "stdafx.h"
#include "cppflibinc.h"
#include "TestMiscFilePath.h"
#include <string.h>

using namespace cppflib;
using namespace cppflib::security::crypto;
using namespace cppflib::collections;
using namespace cppflib::exceptions;
using namespace cppflib::io;
using namespace cppflib::os;

static CFString ByteArray2HexString(ArrayByte &inData)
{
   CFString s;
   for (int i = 0 ; i < static_cast<int>(inData.GetSize()); ++i) {
      u8_t d = inData[i];
      s.AppendFormat(_S("%x%x"), (d >> 4), (d & 0x0f));
   }
   s.MakeLower();
   return s;
}

static void TestLicenseFileDigest(MessageDigest::hashalgo_t hashAlgo, pcwstr_t verifyHashValue)
{
   cppmempool::MemPoolWrap memPool;

   MessageDigest * pMd = new MessageDigest(hashAlgo);
   ArrayByte *pData = new ArrayByte(256);
   // make a digest of the root cert
   FileStream *pStream = new FileStream(CFString(LICENSE_FILE), FileStream::FA_READONLY);
   int nrRead = 0;
   while ((nrRead = pStream->Read(*pData)) > 0) {
       pMd->UpdateBlock(*pData, 0, nrRead);
   }

   ArrayByte *pRetHash = new ArrayByte(static_cast<u32_t>(pMd->GetHashSize()));
   pMd->GetHash(*pRetHash);

   CFString s = ByteArray2HexString(*pRetHash);
   assert(s.Compare(verifyHashValue) == 0);
}

static void TestDigest()
{
   cppmempool::MemPoolWrap memPool;

   debug::Print(_S("Testing message digest ...\n"));

   MessageDigest * pMd = new MessageDigest(MessageDigest::HA_SHA1);
   assert(pMd->GetHashName().CompareNoCase(_S("sha1")) == 0);
   assert(pMd->GetHashSize() == 20);

   char srcData[] = "hello world ! what a nice day ! gogogg";
   int srcLen = strlen(srcData);
   ArrayByte *pData = new ArrayByte(256);
   pData->CopyFrom(reinterpret_cast<u8_t*>(srcData), srcLen, 0, 0, srcLen); 
   ArrayByte *pRetHash = pMd->ComputeHash(*pData, 0, srcLen);
   CFString s = ByteArray2HexString(*pRetHash);
   assert(s.Compare(_S("f006207aa7be5b96c1155edd7c55e2531fe97e2c")) == 0);

   char srcData2[] = "e  eekfk  iid dkkdi idii k dkd ii &&&j jjdjd";
   srcLen = strlen(srcData2);
   pData->CopyFrom(reinterpret_cast<u8_t*>(srcData2), srcLen, 0, 0, srcLen);
   pRetHash = pMd->ComputeHash(*pData, 0, srcLen);
   s = ByteArray2HexString(*pRetHash);
   assert(s.Compare(_S("c83eec36222534dc9d682ca60c2135ac393ae894")) == 0);
 

   TestLicenseFileDigest(MessageDigest::HA_MD5, _S("5c21f83c843650de84f1692a20156bf9"));
   TestLicenseFileDigest(MessageDigest::HA_SHA1, _S("03d93f7538eae63c34752f89f30efe3e5bd293f3"));
   TestLicenseFileDigest(MessageDigest::HA_SHA256, _S("2514772e5475f208616174f81b67168179a7c51bdcb9570a96a9dc5962b83116"));
   TestLicenseFileDigest(MessageDigest::HA_SHA384, _S("51d2bdc921c89d21f9802ddc728ddb9d223a0eb3d37a6ccadda86af42214a27baf4b4715b7e3c4be0a12709b7120e75e"));
   TestLicenseFileDigest(MessageDigest::HA_SHA512, _S("5b7faddb3f407979a127bc4243268ced07b380033897013c2a3e8ec9ac3d3187ce938c70878b0508f7620f4c34144eff644c7c7c9a35a7ef910622dbbced0b8b"));
}

static SymmetricCipher * CreateCipherContext(SymmetricCipher::cipheralgo_t algo, 
                                             SymmetricCipher::blockmode_t mode, bool isEncrypt)
{
   u8_t key[] = {0x00, 0x01, 0x02,0x03,0x04,0x05,0x06,0x07,
                 0x08, 0x09, 0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
                 0x10, 0x11, 0x12,0x13,0x14,0x15,0x16,0x17,
                 0x18, 0x19, 0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,};
   u8_t iv[] = {0x09, 0x08, 0x07, 0x06, 0x09, 0x08, 0x07, 0x06,
                0x09, 0x08, 0x07, 0x06, 0x09, 0x08, 0x07, 0x06,
                0x09, 0x08, 0x07, 0x06, 0x09, 0x08, 0x07, 0x06,
                0x09, 0x08, 0x07, 0x06, 0x09, 0x08, 0x07, 0x06};

   SymmetricCipher *pCipher = new SymmetricCipher(algo, mode, isEncrypt);
   ArrayByte *pKey = new ArrayByte(pCipher->GetKeyLength());
   pKey->CopyFrom(key, sizeof(key), 0, 0, pKey->GetSize());
   ArrayByte *pIV = new ArrayByte(pCipher->GetIVLength());
   pIV->CopyFrom(iv, sizeof(iv), 0, 0, pIV->GetSize());
   pCipher->SetKeyAndIV(*pKey, *pIV);
   return pCipher;
}

static void PrintBuffer(pcwstr_t prefix, ArrayByte &buf, u32_t offset, u32_t len)
{
   debug::PlainPrint(prefix);
   for (u32_t i = 0; i < len; ++i)
      debug::PlainPrint(_S("%02x "), buf[offset + i]);
   debug::PlainPrint(_S("\r\n"));
}

static void PrintCipherInfo(SymmetricCipher &cipher)
{
   u32_t s = cipher.GetBlockSize();
   debug::PlainPrint(_S("GetBlockSize = %u\n"), s);
   s = cipher.GetKeyLength();
   debug::PlainPrint(_S("GetKeyLength = %u\n"), s);
   s = cipher.GetIVLength();
   debug::PlainPrint(_S("GetIVLength = %u\n"), s);
}

static ArrayByte * EncryptData(SymmetricCipher *pCipher, ArrayByte *pData)
{
   ArrayByte *outBuf = new ArrayByte(((pData->GetSize() + pCipher->GetBlockSize()) / pCipher->GetBlockSize()) * pCipher->GetBlockSize());
   u32_t start = 0;
   u32_t outStart = 0;
   u32_t offsetUpdate = 0;
   while (start < pData->GetSize()) {
      u32_t len = cppflib::Min<u32_t>(pData->GetSize() - start, pCipher->GetBlockSize());
      if (len < pCipher->GetBlockSize()) {
         break;
      }
      else {
         pCipher->EncryptUpdate(*outBuf, outStart, *pData, start, offsetUpdate);
         //PrintBuffer(_S("outBuf = "), *outBuf, outStart, len);
      }
      start += len;
      outStart += offsetUpdate;
   }

   u32_t lastBlockLen = cppflib::Min<u32_t>(pData->GetSize() - start, pCipher->GetBlockSize());
   if (!pCipher->EncryptFinal(*outBuf, outStart, *pData, start, lastBlockLen, offsetUpdate))
      debug::PlainPrint(_S("Error EncryptFinal\r\n"));
   //PrintBuffer(_S("finalBlock = "), *outBuf, outStart, offsetUpdate);
   return outBuf;
}

static ArrayByte * DecryptData(SymmetricCipher *pCipher, ArrayByte *pEncryptedData, u32_t &finalLen)
{
   ArrayByte *outBuf = new ArrayByte(pEncryptedData->GetSize());
   u32_t start = 0;
   u32_t outStart = 0;
   u32_t offsetUpdate = 0;
   while (start < pEncryptedData->GetSize()) {
      u32_t len = pCipher->GetBlockSize();   
      pCipher->DecryptUpdate(*outBuf, outStart, *pEncryptedData, start, offsetUpdate);
      //PrintBuffer(_S("outBuf = "), *outBuf, outStart, offsetUpdate);
      start += len;
      outStart += offsetUpdate;
   }

   if (!pCipher->DecryptFinal(*outBuf, outStart, offsetUpdate))
      debug::PlainPrint(_S("Error EncryptFinal\r\n"));
   //PrintBuffer(_S("finalBlock = "), *outBuf, outStart, offsetUpdate);
   outStart += offsetUpdate;
   finalLen = outStart;
   return outBuf;
}

static void CompareData(ArrayByte &orgData, ArrayByte &decryptedData, u32_t cmpSize)
{
   for (u32_t i = 0; i < cmpSize; ++i) {
      if (orgData[i] != decryptedData[i]) {
         CFString s;
         s.Format(_S("%d -- character different %02x/%02x"), i, orgData[i], decryptedData[i]);
         throw ApplicationException(s);
      }
   }
}

static void __TestSymmetricCipher(pcwstr_t testString, 
                                  SymmetricCipher::cipheralgo_t cipherAlgo, 
                                  SymmetricCipher::blockmode_t blockMode)
{
   cppmempool::MemPoolWrap memPool;

   
   try {
       SymmetricCipher *pCipher = CreateCipherContext(cipherAlgo, blockMode, true);
       //PrintCipherInfo(*pCipher);

       CFString dataStr(testString);
       int utf8Len = 0;
       dataStr.ToUtf8String(NULL, 0, &utf8Len);
       ArrayByte *pData = new ArrayByte(utf8Len + 1);
       dataStr.ToUtf8String(reinterpret_cast<char*>(pData->GetRawPtr()), 
                            static_cast<int>(pData->GetSize()), &utf8Len);
       // encrypt
       ArrayByte *pRawData = new ArrayByte(utf8Len);
       pRawData->CopyFrom(*pData, pData->GetSize() - 1);
       ArrayByte *outBuf = EncryptData(pCipher, pRawData);
       
       // decrypt
       u32_t finalLen = 0;
       SymmetricCipher *pDecryptCipher = CreateCipherContext(cipherAlgo, blockMode, false);
       ArrayByte *decryptedOutBuf = DecryptData(pDecryptCipher, outBuf, finalLen);

       CompareData(*pRawData, *decryptedOutBuf, finalLen);
   }
   catch (BaseException &e) {
      debug::PlainPrint(_S("Error: %s\n"), e.GetMsg());
      assert(false);
   }
}

static void TestSymmetricCipher(SymmetricCipher::cipheralgo_t cipherAlgo, 
                                SymmetricCipher::blockmode_t blockMode)
{
   
   pcwstr_t strArray[] = {
                            _S("You may not know who Lars Rasmussen is. But chances are, more than once he helped get you where you were going."),
                            _S("You may "),
                            _S("You may not know"),
                            _S("You may not know who Lars Rasmuss"),
                            NULL
                         };
   pcwstr_t * pArray = strArray;
   while (*pArray) {
      __TestSymmetricCipher(*pArray++, cipherAlgo, blockMode);
   }
}

static void TestCipher()
{
   debug::Print(_S("Testing symmetric cipher ...\n"));

   cppmempool::MemPoolWrap memPool;

   // this one is no use, just init the library code behind
   SymmetricCipher *pCipher = new SymmetricCipher(SymmetricCipher::CA_AES128, 
                                                  SymmetricCipher::BM_CBC, true);

   TestSymmetricCipher(SymmetricCipher::CA_AES128, SymmetricCipher::BM_CBC);
   TestSymmetricCipher(SymmetricCipher::CA_AES192, SymmetricCipher::BM_CBC);
   TestSymmetricCipher(SymmetricCipher::CA_AES256, SymmetricCipher::BM_CBC);
   
   TestSymmetricCipher(SymmetricCipher::CA_DES, SymmetricCipher::BM_CBC);
   // three 64 bit keys -- pack the 3 keys into one array and set using SetKey or SetKeyAndIV
   TestSymmetricCipher(SymmetricCipher::CA_3DES3, SymmetricCipher::BM_CBC);

   TestSymmetricCipher(SymmetricCipher::CA_IDEA, SymmetricCipher::BM_CBC);
   TestSymmetricCipher(SymmetricCipher::CA_RC2, SymmetricCipher::BM_CBC);

   TestSymmetricCipher(SymmetricCipher::CA_CAMELLIA128, SymmetricCipher::BM_CBC);
   TestSymmetricCipher(SymmetricCipher::CA_CAMELLIA256, SymmetricCipher::BM_CBC);
   
   TestSymmetricCipher(SymmetricCipher::CA_SEED, SymmetricCipher::BM_CBC);
}

void TestCrypto(void)
{
   TestDigest();  
   TestCipher(); 
}
